/******************************************************************************
*   Project:		DotNetCRM 
*	Filename:		DomainObject.cs
*	Author:			Kristopher Cargile <kris@kriscargile.com>
*	Date Created:	July 02, 2008
*******************************************************************************
*	Change History: 
*******************************************************************************
* IMPORTANT GNU LICENSE INFORMATION. DO NOT REMOVE THIS SECTION!
*
* DotNetCRM :: Copyright (c) 2005-2010, Cargile Technology Group, LLC
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
******************************************************************************/

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using DotNetCRM.Core.Properties;
using Microsoft.Practices.EnterpriseLibrary.Validation;
using System.Data;

namespace DotNetCRM.Core
{
	/// <summary>
	/// A domain object.
	/// </summary>
    [Serializable]
    public abstract class DomainObject : ICloneable
	{
        /// <summary>
        /// Gets or sets the entity id.
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// True if this record is active; otherwise, false.
        /// </summary>
        public bool IsActive { get; set; }

        /// <summary>
        /// Gets or sets the id of the user who created this record.
        /// </summary>
        public string CreatedBy { get; protected set; }

        /// <summary>
        /// Gets or sets the id of the user who last modified this record.
        /// </summary>
        public string ModifiedBy { get; protected set; }

        /// <summary>
        /// Gets the date the record was created.
        /// </summary>
        public DateTime Created { get; protected set; }

        /// <summary>
        /// Gets the date the record was last modified.
        /// </summary>
        public DateTime Modified { get; protected set; }

        /// <summary>
        /// Gets the record version.
        /// </summary>
        public int RecordVersion { get; protected set; }

        /// <summary>
        /// Gets or sets <see cref="Meta"/> items associated with this entity.
        /// </summary>
        public List<Meta> UserDefined { get; set; }

        /// <summary>
        /// Gets or sets the description of this object.
        /// </summary>
        public string Description { get; set; }

        /// <summary>
        /// Initialize a new instance of the DomainEntity class.
        /// </summary>
        protected DomainObject()
        {
            Created = Modified = DateTime.Now;
            RecordVersion = -1;

            if (!string.IsNullOrEmpty(Thread.CurrentPrincipal.Identity.Name))
            {
                ModifiedBy = CreatedBy = Thread.CurrentPrincipal.Identity.Name;
            }
            else
            {
                ModifiedBy = CreatedBy = Environment.UserName;
            }
        }

        /// <summary>
        /// Loads the object with the specified id.
        /// </summary>
        /// <param name="id">Id of the object to load.</param>
	    public abstract void Load(int id);

        /// <summary>
        /// Adds or updates the object. 
        /// </summary>
        /// <returns>Number of records affected.</returns>
        public abstract int Save();

        /// <summary>
        /// Deletes the object from persistent storage. 
        /// </summary>
        /// <returns>Number of records affected.</returns>
        public abstract int Delete();

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            return Id.GetHashCode();
        }

        /// <summary>
        /// Determines whether the specified DomainObject object is equal to 
        /// the current DomainObject object instance.
        /// </summary>
        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }

            if (!(obj.GetType() == GetType()))
            {
                throw new ArgumentException(string.Format(Messages.ex_invalidComparisonType, GetType()));
            }

            bool areEqual = true;
            foreach (PropertyInfo p in GetType().GetProperties())
            {
                object tval = p.GetValue(this, null);
                object oval = p.GetValue(obj, null);

                if (tval == null && oval == null)
                {
                    continue;
                }

                if ((tval == null) || (oval == null) ||
                    !p.GetValue(this, null).Equals(p.GetValue(obj, null)))
                {
                    areEqual = false;
                    break;
                }
            }

            return areEqual;
        }

        #region Validation

        /// <summary>
        /// Validates this instance using the default ruleset.
        /// </summary>
        /// <returns><see cref="ValidationResults"/> containing the validation results.</returns>
        public virtual ValidationResults Validate()
        {
            return Validate(this);
        }

        /// <summary>
        /// Validates the specified object using the default ruleset.
        /// </summary>
        /// <typeparam name="T">Type of the object to validate.</typeparam>
        /// <param name="obj">The object to validate.</param>
        /// <returns><see cref="ValidationResults"/> containing the validation results.</returns>
        public static ValidationResults Validate<T>(T obj) where T : DomainObject
        {
            if (obj == null)
            {
                throw new ArgumentNullException("obj");
            }

            throw new NotImplementedException();
        }

        #endregion

        #region Dataset Conversion

        /// <summary>
        /// Converts the provided list to a <see cref="DataSet"/>.
        /// </summary>
        /// <typeparam name="T">Concrete type of the objects in the list.</typeparam>
        /// <param name="list">The list to convert.</param>
        /// <returns>A <see cref="DataSet"/> containing the converted records.</returns>
        public static DataSet ToDataSet<T>(IList<T> list) where T : DomainObject
        {
            if (list == null)
            {
                throw new ArgumentNullException("list");
            }

            DataSet ds = new DataSet();

            if (list.Count <= 0)
            {
                return ds;
            }

            DataTable dt = new DataTable(typeof(T).Name);

            PropertyInfo[] propertyInfo = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

            foreach (T t in list)
            {
                if (t == null)
                {
                    continue;
                }

                DataRow row = dt.NewRow();
                for (int i = 0, j = propertyInfo.Length; i < j; i++)
                {
                    PropertyInfo pInfo = propertyInfo[i];
                    string name = pInfo.Name;
                    if (dt.Columns[name] == null)
                    {
                        dt.Columns.Add(new DataColumn(name, pInfo.PropertyType));
                    }
                    row[name] = pInfo.GetValue(t, null);
                }
                dt.Rows.Add(row);
            }
            ds.Tables.Add(dt);
            return ds;
        }

        /// <summary>
        /// Converts the provided list to a <see cref="DataTable"/>.
        /// </summary>
        /// <typeparam name="T">Concrete type of the objects in the list.</typeparam>
        /// <param name="list">The list to convert.</param>
        /// <returns>A <see cref="DataTable"/> containing the converted records.</returns>
        public static DataTable ToDataTable<T>(IList<T> list) where T : DomainObject
        {
            if (list == null)
            {
                throw new ArgumentNullException("list");
            }

            DataSet ds = ToDataSet(list);
            DataTable dt = null;

            if (ds != null && ds.Tables.Count > 0)
            {
                dt = ds.Tables[0];
            }

            return dt;
        }

        #endregion

        #region ICloneable Members

        /// <summary>
        /// Creates a memberwise clone of this object.
        /// </summary>
        /// <returns>A new instance of this object.</returns>
        public object Clone()
        {
            // TODO: populate object properties using reflection
            // TOOD: make sure Id, Dates, and RecordVersion are nulled

            throw new NotImplementedException();
        }

        #endregion

        #region Operator Overloads

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="obj1">The obj1.</param>
        /// <param name="obj2">The obj2.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator ==(DomainObject obj1, DomainObject obj2)
        {
            return obj1.Equals(obj2);
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="obj1">The obj1.</param>
        /// <param name="obj2">The obj2.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator !=(DomainObject obj1, DomainObject obj2)
        {
            return !(obj1 == obj2);
        }

        #endregion
    }
}